//== SAGITTARIUS ============================================================================

//== BEGINNING OF CODE ===============================================================

#include "sagittarius/core/SagittariusCore.h"
#include "sagittarius/patterns/hashtable_th.h"
#include "sagittarius/hash/hash.h"
#include "sagittarius/io/io.h"
#include <malloc.h>
#include <string.h>

#if SAGITTARIUS_COMPILER == SAGITTARIUS_COMPILER_GNUC || SAGITTARIUS_COMPILER == SAGITTARIUS_COMPILER_MINGW
inline
#endif
hashkv_pair64 makehashkv_pair64(void* p, size_t len, size_t copy_len) {
    hashkv_pair64 r;
	r.len = len;
    if(copy_len) {
        r.v = (void*)malloc(copy_len);
        memcpy(r.v, p, copy_len);
    } else
        r.v = p;
    r.k = sg_hash64(r.v, len);
    return r;
}

void* _ht64_get(hashtable64* ht, hashkv_pair64 e) {
    hashbucket64* b = &ht->buckets[e.k%ht->nbuckets];
    uint32 i;
    
    RLSEMA(b->sema);
    
    for(i=0; i<b->n; ++i) {
        if(b->e[i].v == e.v) {
            RUSEMA(b->sema);
            return b->e[i].v;
        }
    }
    
    RUSEMA(b->sema);
    
    return NULL;
}

void* ht64_get(hashtable64* ht, void* p, size_t len) {
    return _ht64_get(ht, makehashkv_pair64(p, len, 0));
}

int _ht64_check(hashtable64* ht, hashkv_pair64 e) {
    if(_ht64_get(ht, e))
        return 1;
    else
        return 0;
}

int ht64_check(hashtable64* ht, void* p, size_t len) {
    return _ht64_check(ht, makehashkv_pair64(p, len, 0));
}

int _valuecompare(void* p, void* q, size_t len) {
    size_t i;
    for(i=0; i<len; ++i) {
        if(((uint8*)p)[i] != ((uint8*)q)[i])
            return 0;
    }
    return 1;
}

int _ht64_insert(hashtable64* ht, hashkv_pair64 e) {
    //printf("_ht64_insert key = %lu, nbuckets = %u, result %u\n", e.k, ht->nbuckets, e.k%ht->nbuckets);
    hashbucket64* b = NULL;
    size_t bn;
    hashkv_pair64* tmp;
    uint32 i;
    uint32 depth;
    
    RLSEMA(ht->sema); //no modifications to the hash table internals while this is running
    
    bn = e.k%ht->nbuckets;
    b = &ht->buckets[bn]; //NOTE: not safe to do this before ht sema is locked
    
    WLSEMA(b->sema);
    
    /*SG_WRITE(2, "  adding: ");
    sg_writeMemSegHex(2, e.v, e.len);
    SG_WRITE(2, "\n");
    SG_WRITE(2, "  key: ");
    sg_writeUInt(2, e.k);
    SG_WRITE(2, "\n");
    SG_WRITE(2, "  bucket number: ");
    sg_writeUInt(2, bn);
    SG_WRITE(2, "\n");*/
    
    for(i=0; i<b->n; ++i) {
        if(b->e[i].len == e.len) {
            if(_valuecompare(b->e[i].v, e.v, e.len)) {
                WUSEMA(b->sema);
                RUSEMA(ht->sema);
                /*SG_WRITE(2, "  collision with: ");
                sg_writeMemSegHex(2, b->e[i].v, b->e[i].len);
                SG_WRITE(2, "\n");*/
                return HASH_COLLISION;
            }
        }
    }
    if(b->n >= b->depth) {
        //need to reallocate
        depth = b->depth<<1;
        tmp = malloc(depth*sizeof(hashkv_pair64));
        memcpy(tmp, b->e, b->depth*sizeof(hashkv_pair64));
        b->depth = depth;
        free(b->e);
        b->e = tmp;
    }
    b->e[b->n] = e;
    ++b->n;
    
    WUSEMA(b->sema);
    RUSEMA(ht->sema);
    
    return HASH_SUCCESS;
}

int ht64_insert(hashtable64* ht, void* p, size_t len, size_t copy_len) {
    return _ht64_insert(ht, makehashkv_pair64(p, len, copy_len));
}

hashtable64* ht64_rehash(hashtable64* ht, uint32 nbuckets, uint32 depth) {
    hashtable64* h;
    hashbucket64* b;
    uint32 i, j;
    
    h = malloc(sizeof(hashtable64));
    AN(h);
    WLSEMA(h->sema);
    
    h->nbuckets = nbuckets;
    h->buckets = malloc(nbuckets*sizeof(hashbucket64));
    for(i=0; i<h->nbuckets; ++i) {
        WLSEMA(h->buckets[i].sema);
        h->buckets[i].n = 0;
        h->buckets[i].depth = depth;
        h->buckets[i].e = malloc(depth*sizeof(hashkv_pair64));
        WUSEMA(h->buckets[i].sema);
    }
    
    //insert old values
    if(ht != NULL) {
        for(i=0; i<ht->nbuckets; ++i) {
            b = &ht->buckets[i];
            for(j=0; j<b->n; ++j) {
                _ht64_insert(h, b->e[j]);
            }
        }
    }
    
    WUSEMA(h->sema);
    return h;
}

void ht64_destroy(hashtable64* h) {
    uint32 i;
    for(i=0; i<h->nbuckets; ++i) {
        free(h->buckets[i].e);
    }
    free(h->buckets);
    free(h);
}

void ht64_dumpstate(int fd, hashtable64* h) {
    uint32 i, j;
    SG_WRITE(fd, "Hash Table64");
    SG_WRITE(fd, "\n  No buckets: ");
    sg_writeInt(fd, (int)h->nbuckets);
    SG_WRITE(fd, "\n");
    for(i=0; i<h->nbuckets; ++i) {
        SG_WRITE(fd, "    Bucket ");
        sg_writeUInt(fd, i);
        SG_WRITE(fd, "\n      Number ");
        sg_writeUInt(fd, h->buckets[i].n);
        SG_WRITE(fd, "\n      Depth ");
        sg_writeUInt(fd, h->buckets[i].depth);
        SG_WRITE(fd, "\n");
        
        for(j=0; j<h->buckets[i].n; ++j) {
            SG_WRITE(fd, "        Key ");
            sg_writeMemSegHex(fd, &h->buckets[i].e[j].k, 8);
            SG_WRITE(fd, "\n        Value ");
            sg_writeMemSegHex(fd, h->buckets[i].e[j].v, 8);
            SG_WRITE(fd, "\n");
        }
    }
}